package org.liurb.springboot.starter.gson.resolver;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import com.google.gson.JsonObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.liurb.springboot.starter.common.utils.RequestHttpUtil;
import org.liurb.springboot.starter.gson.annotation.GsonParser;
import org.liurb.springboot.starter.gson.component.GsonManager;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;


import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * GsonParser注解参数解析器
 *
 * 主要用于解析"非json"请求方式上加上GsonParser注解的参数，使其对gson相关注解生效
 * 兼容Valid注解
 *
 * @Author Liurb
 * @Date 2022/11/30
 */
@Slf4j
@Component
public class GsonParserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        //适配GsonParser注解
        return methodParameter.hasParameterAnnotation(GsonParser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {

        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);

        if (request.getMethod().equals("GET")) {

            //获取get请求参数
            String queryParams = request.getQueryString();
            log.info("原Get请求query信息: {}", queryParams);
            //转换为map
            Map<String, String> paramMap = HttpUtil.decodeParamMap(queryParams, StandardCharsets.UTF_8);
            //转json
            JsonObject paramJson = GsonManager.getInstance().stringMapToJson(paramMap);
            //转换为对应的方法参数bean
            Object obj = this.mapToBean(paramJson, methodParameter, nativeWebRequest, webDataBinderFactory);

            return obj;

        } else if (request.getMethod().equals("POST")) {

            //获取post请求参数中所有的信息
            Map<String, Object> paramMap = RequestHttpUtil.getPostRequestData(request);

            //转json
            JsonObject paramJson = GsonManager.getInstance().toJsonObject(paramMap);

            //转换为对应的方法参数bean
            Object obj = this.mapToBean(paramJson, methodParameter, nativeWebRequest, webDataBinderFactory);

            return obj;

        }

        return null;
    }

    /**
     * 请求参数转化为对应的bean
     *
     * @param paramObject
     * @param methodParameter
     * @param nativeWebRequest
     * @param webDataBinderFactory
     * @return
     * @throws Exception
     */
    private Object mapToBean(JsonObject paramObject, MethodParameter methodParameter, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        //将拦截转换好的JSONObject参数，转换为注解FastJsonParser上的请求参数bean
        String jsonText = GsonManager.getInstance().toJsonText(paramObject);
        Object obj = GsonManager.getInstance().convert(jsonText, methodParameter.getParameterType());

        //存在映射字段
        if (ObjectUtil.isNotEmpty(obj)) {
            //获取到入参的名称
            String name = methodParameter.getParameterName();
            //只有存在binderFactory才会去完成自动的绑定、校验
            if (webDataBinderFactory != null) {
                WebDataBinder binder = webDataBinderFactory.createBinder(nativeWebRequest, obj, name);
                if (binder.getTarget() != null) {
                    // 如果使用了validation校验, 则进行相应校验
                    if (methodParameter.hasParameterAnnotation(Valid.class)) {
                        // 如果有校验报错，会将结果放在binder.bindingResult属性中
                        binder.validate();
                    }

                    // 如果参数中不包含BindingResult参数，直接抛出异常
                    if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, methodParameter)) {
                        throw new BindException(binder.getBindingResult());
                    }

                }
            }
        }

        return obj;
    }

    /**
     * 检查参数中是否包含BindingResult参数
     *
     * @param binder
     * @param methodParam
     * @return
     */
    protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
        int i = methodParam.getParameterIndex();
        Class[] paramTypes = methodParam.getMethod().getParameterTypes();
        boolean hasBindingResult = paramTypes.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]);
        return !hasBindingResult;
    }
}
